// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/common/cursors/webcursor.h"

#include "base/logging.h"
#include "base/pickle.h"
#include "build/build_config.h"
#include "third_party/WebKit/public/platform/WebImage.h"

using blink::WebCursorInfo;

static const int kMaxCursorDimension = 1024;

namespace content {

WebCursor::WebCursor()
    : type_(WebCursorInfo::TypePointer)
    , custom_scale_(1)
{
    InitPlatformData();
}

WebCursor::~WebCursor()
{
    Clear();
}

WebCursor::WebCursor(const WebCursor& other)
{
    InitPlatformData();
    Copy(other);
}

const WebCursor& WebCursor::operator=(const WebCursor& other)
{
    if (this == &other)
        return *this;

    Clear();
    Copy(other);
    return *this;
}

void WebCursor::InitFromCursorInfo(const CursorInfo& cursor_info)
{
    Clear();

    type_ = cursor_info.type;
    hotspot_ = cursor_info.hotspot;
    if (IsCustom())
        SetCustomData(cursor_info.custom_image);
    custom_scale_ = cursor_info.image_scale_factor;
    CHECK(custom_scale_ > 0);
    ClampHotspot();
}

void WebCursor::GetCursorInfo(CursorInfo* cursor_info) const
{
    cursor_info->type = static_cast<WebCursorInfo::Type>(type_);
    cursor_info->hotspot = hotspot_;
    ImageFromCustomData(&cursor_info->custom_image);
    cursor_info->image_scale_factor = custom_scale_;
}

bool WebCursor::Deserialize(base::PickleIterator* iter)
{
    int type, hotspot_x, hotspot_y, size_x, size_y, data_len;
    float scale;
    const char* data;

    // Leave |this| unmodified unless we are going to return success.
    if (!iter->ReadInt(&type) || !iter->ReadInt(&hotspot_x) || !iter->ReadInt(&hotspot_y) || !iter->ReadLength(&size_x) || !iter->ReadLength(&size_y) || !iter->ReadFloat(&scale) || !iter->ReadData(&data, &data_len))
        return false;

    // Ensure the size is sane, and there is enough data.
    if (size_x > kMaxCursorDimension || size_y > kMaxCursorDimension)
        return false;

    // Ensure scale isn't ridiculous, and the scaled image size is still sane.
    if (scale < 0.01 || scale > 100 || size_x / scale > kMaxCursorDimension || size_y / scale > kMaxCursorDimension)
        return false;

    type_ = type;

    if (type == WebCursorInfo::TypeCustom) {
        if (size_x > 0 && size_y > 0) {
            // The * 4 is because the expected format is an array of RGBA pixel
            // values.
            if (size_x * size_y * 4 != data_len) {
                LOG(WARNING) << "WebCursor's data length and image size mismatch: "
                             << size_x << "x" << size_y << "x4 != "
                             << data_len;
                return false;
            }

            hotspot_.set_x(hotspot_x);
            hotspot_.set_y(hotspot_y);
            custom_size_.set_width(size_x);
            custom_size_.set_height(size_y);
            custom_scale_ = scale;
            ClampHotspot();

            custom_data_.clear();
            if (data_len > 0) {
                custom_data_.resize(data_len);
                memcpy(&custom_data_[0], data, data_len);
            }
        }
    }
    return DeserializePlatformData(iter);
}

bool WebCursor::Serialize(base::Pickle* pickle) const
{
    if (!pickle->WriteInt(type_) || !pickle->WriteInt(hotspot_.x()) || !pickle->WriteInt(hotspot_.y()) || !pickle->WriteInt(custom_size_.width()) || !pickle->WriteInt(custom_size_.height()) || !pickle->WriteFloat(custom_scale_))
        return false;

    const char* data = NULL;
    if (!custom_data_.empty())
        data = &custom_data_[0];
    if (!pickle->WriteData(data, custom_data_.size()))
        return false;

    return SerializePlatformData(pickle);
}

bool WebCursor::IsCustom() const
{
    return type_ == WebCursorInfo::TypeCustom;
}

bool WebCursor::IsEqual(const WebCursor& other) const
{
    if (type_ != other.type_)
        return false;

    if (!IsPlatformDataEqual(other))
        return false;

    return hotspot_ == other.hotspot_ && custom_size_ == other.custom_size_ && custom_scale_ == other.custom_scale_ && custom_data_ == other.custom_data_;
}

void WebCursor::Clear()
{
    type_ = WebCursorInfo::TypePointer;
    hotspot_.set_x(0);
    hotspot_.set_y(0);
    custom_size_.set_width(0);
    custom_size_.set_height(0);
    custom_scale_ = 1;
    custom_data_.clear();
    CleanupPlatformData();
}

void WebCursor::Copy(const WebCursor& other)
{
    type_ = other.type_;
    hotspot_ = other.hotspot_;
    custom_size_ = other.custom_size_;
    custom_scale_ = other.custom_scale_;
    custom_data_ = other.custom_data_;
    CopyPlatformData(other);
}

void WebCursor::SetCustomData(const SkBitmap& bitmap)
{
    CreateCustomData(bitmap, &custom_data_, &custom_size_);
}

void WebCursor::CreateCustomData(const SkBitmap& bitmap,
    std::vector<char>* custom_data,
    gfx::Size* custom_size)
{
    if (bitmap.empty())
        return;

    // Fill custom_data directly with the NativeImage pixels.
    custom_data->resize(bitmap.getSize());
    if (!custom_data->empty()) {
        //This will divide color values by alpha (un-premultiply) if necessary
        SkImageInfo dstInfo = bitmap.info().makeAlphaType(kUnpremul_SkAlphaType);
        bitmap.readPixels(dstInfo, &(*custom_data)[0], dstInfo.minRowBytes(), 0, 0);
    }
    custom_size->set_width(bitmap.width());
    custom_size->set_height(bitmap.height());
}

void WebCursor::ImageFromCustomData(SkBitmap* image) const
{
    if (custom_data_.empty())
        return;

    SkImageInfo image_info = SkImageInfo::MakeN32(custom_size_.width(),
        custom_size_.height(),
        kUnpremul_SkAlphaType);
    if (!image->tryAllocPixels(image_info))
        return;
    memcpy(image->getPixels(), &custom_data_[0], custom_data_.size());
}

void WebCursor::ClampHotspot()
{
    if (!IsCustom())
        return;

    // Clamp the hotspot to the custom image's dimensions.
    hotspot_.set_x(std::max(0,
        std::min(custom_size_.width() - 1, hotspot_.x())));
    hotspot_.set_y(std::max(0,
        std::min(custom_size_.height() - 1, hotspot_.y())));
}

} // namespace content
